
#ifndef TM1637_H
#define TM1637_H

#include <inttypes.h>
#include <stdbool.h>
#include <driver/gpio.h>
#include "rom/ets_sys.h"
#include <cmath>

class TM1637
{
private:
    int _segment_idx[6]; // 数码管模块实际索引映射
    int _segment_start;
    int _segment_max;    // 支持的最多数码管位数。4位或6位。
    gpio_num_t _pin_clk; // clk pin
    gpio_num_t _pin_dta; // data pin
    uint8_t _brightness; // 亮度值

    // 定义几个command常量
    enum command1
    {
        _ADDR_AUTO = 0x40,  // 自动地址增加模式命令
        _ADDR_FIXED = 0x44, // 固定地址模式命令
        _AUTO_DELAY = 300000,
    };

    // 起始信号
    inline void _start()
    {
        gpio_set_level(_pin_clk, 1);
        gpio_set_level(_pin_dta, 1);
        _delay();
        gpio_set_level(_pin_dta, 0);
        _delay();
    };

    // 应答信号
    void _ack();

    // 停止信号
    inline void _stop()
    {
        gpio_set_level(_pin_clk, 0);
        _delay();
        gpio_set_level(_pin_dta, 0);
        _delay();
        gpio_set_level(_pin_clk, 1);
        _delay();
        gpio_set_level(_pin_dta, 1);
        _delay();
    };

    inline void _delay()
    {
        ets_delay_us(1);
    };

    void _send_byte(uint8_t byte); // 发送一个字节的数据

    // 将一个浮点数val按指定的精度precision进行四舍五入取近似值。
    // precision是小数点后的位数。
    inline static float nearestf(float val, int precision)
    {
        int scale = pow(10, precision);
        return roundf(val * scale) / scale;
    }

    // 取得关于str字符串的映像，映像大小最好限定为12个字节。
    // 必须传入image的实际容量大小arrSize。
    static void get_image(const char *str, uint8_t *image, size_t arrSize);

public:
    TM1637() {}

    // 如果是四位数码管则four_Segment设置为true，如果设置成false，则是6位数码管。
    TM1637(gpio_num_t pin_clk, gpio_num_t pin_dta, bool four_segment);

    void init(gpio_num_t pin_clk, gpio_num_t pin_dta, bool four_segment);

    // 设置亮度
    void set_brightness(uint8_t level);

    // 使指定地址segment_idx的数码管显示指定的一个字节数据data。
    // 地址指的是数码管编号，比如4位数码管从左到右编号0、1、2、3.
    void set_segment_fixed(const int8_t segment_idx, const uint8_t data);

    // 自动地址增加模式显示数据
    void set_segment_auto(const uint8_t *data, const int data_length);

    // 在指定的一位数码管上显示数字，同时处理该管的小数点。
    // segment_idx 数码管索引，从左到右(0...5)
    // num 待显示的数字(0..9)
    // dot 是否显示本位的小数点
    void disp_number_one_segment(const int8_t segment_idx, const uint8_t num, const bool dot);    

    // 显示字符串.
    // 如果left=true则采用左对齐方式，否则右对齐。但如果str长度超过数码管位数则无视left设置，一律采用左对齐。
    // 超过最大数码管数量的字符将被截断。
    // 为了配合显示浮点数中的小数点，dot_idx被指定为需要显示小数点的数码管地址索引，也即实际小数点所在位置的前面一位。
    // 注意，如果是显示浮点数调用，则str本身需要不含小数点。
    void disp_string(const char *str, bool left, int dot_idx = -1);

    // 字符串从数码管的右侧平滑进入显示。
    // dot_idx的作用和disp_string()中的dot_idx一样。
    void disp_slide(const char *str, int dot_idx = -1);

    // 显示数字
    // 如果silid=true，则跑马灯显示，否则只显示数码管位数的数字，之后的被截断。
    // 规定：只有带小数点的数码管才能显示小数点，不采用用symbols.h中定义的短划线。
    void disp_num(const int num, bool slide);
    void disp_num(const double num, bool slide);

    // 显示double数据，小数点不用字符表中的映像表示，而是数码管本身的bit7，该段是小数点段。
    void disp_num_with_dot(const double num, bool slide);
    
    // 清屏
    void disp_clear();
};

#endif